package com.elitesland.b2c.com.service;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.elitesland.b2c.com.dto.ComFileDTO;
import com.elitesland.b2c.com.entity.ComFileDO;
import com.elitesland.b2c.com.repo.ComFileRepo;
import com.elitesland.b2c.com.repo.ComFileRepoProc;
import com.elitesland.b2c.com.rpc.RpcFileService;
import com.elitesland.b2c.com.rpc.file.param.StartFileUploadVO;
import com.elitesland.b2c.com.vo.resp.ComFileUploadRespVO;
import com.elitesland.core.base.ApiResult;
import com.elitesland.core.exception.BusinessException;
import com.elitesland.model.SysConfigProperties;
import net.coobird.thumbnailator.Thumbnails;
import net.coobird.thumbnailator.util.ThumbnailatorUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author wang shao
 * @since 2021/6/16
 */
@Service
public class ComFileServiceImpl implements ComFileService {

    /**
     * 支持转缩略图的后缀
     */
    private final List<String> supportThumbSuffix = ThumbnailatorUtils.getSupportedOutputFormats();

    @Transactional(rollbackFor = Exception.class)
    @Override
    public ApiResult<ComFileUploadRespVO> upload(MultipartFile multipartFile) {
        ComFileDO fileDO = null;
        try {
            fileDO = obtainFileDo(multipartFile);
        } catch (Exception e) {
            logger.info("上传文件校验不通过：{}", e.getMessage());
            return ApiResult.fail(e.getMessage());
        }

        // 如果是图片，则先上传缩略图
        uploadThumb(multipartFile, fileDO);

        // 再上传原文件
        uploadOriginal(multipartFile, fileDO);

        // 持久化文件信息
        fileUploadInfoRepository.save(fileDO);

        // 返回上传结果
        ComFileUploadRespVO respVO = new ComFileUploadRespVO();
        respVO.setId(fileDO.getId());
        respVO.setCode(fileDO.getFileCode());
        respVO.setName(fileDO.getOriginalName());
        respVO.setSize(fileDO.getFileSize());
        return ApiResult.ok(respVO);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public ApiResult<Long> delete(Long id) {
        if (id == null) {
            return ApiResult.fail("文件ID为空");
        }
        Optional<ComFileDO> optionalFileDO = fileUploadInfoRepository.findById(id);
        if (optionalFileDO.isEmpty()) {
            return ApiResult.fail("文件不存在");
        }
        ComFileDO fileDO = optionalFileDO.get();

        // 先删除数据后删除文件
        fileUploadInfoRepository.deleteById(id);
        try {
            if (StrUtil.isNotBlank(fileDO.getThumbnailFileCode())) {
                // 如果有缩略图，则先删除缩略图
                rpcFileService.delete(fileDO.getThumbnailFileCode());
            }
            rpcFileService.delete(fileDO.getFileCode());
        } catch (Exception e) {
            logger.info("调用文件服务接口删除文件失败：{}", e.getMessage());
        }
        return ApiResult.ok(id);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public ApiResult<List<Long>> delete(List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return ApiResult.fail("文件ID为空");
        }

        List<String> codes = comFileRepoProc.getFileCodes(ids);
        if (CollectionUtils.isEmpty(codes)) {
            return ApiResult.fail("文件不存在");
        }

        // 先删除数据后删除文件
        fileUploadInfoRepository.deleteAllByIdIn(ids);
        try {
            rpcFileService.delete(codes);
        } catch (Exception e) {
            logger.info("调用文件服务接口删除文件失败：{}", e.getMessage());
        }
        return ApiResult.ok(ids);
    }

    @Override
    public ApiResult<Long> download(Long id, OutputStream outputStream) {
        if (id == null) {
            return ApiResult.fail("文件ID为空");
        }

        String code = comFileRepoProc.getFileCode(id);
        if (StrUtil.isBlank(code)) {
            return ApiResult.fail("文件不存在");
        }

        try {
            rpcFileService.downloadStreaming(code, outputStream);
        } catch (Exception e) {
            logger.error("下载文件失败：", e);
            return ApiResult.fail("下载文件失败！");
        }
        return null;
    }

    @Override
    public ApiResult<String> show(OutputStream outputStream, Long id, Boolean thumbnail) {
        if (id == null) {
            return ApiResult.fail("文件ID为空");
        }

        thumbnail = thumbnail != null && thumbnail;

        String fileCode = null;
        if (thumbnail) {
            fileCode = comFileRepoProc.getThumbnailFileCode(id);
        }
        if (StrUtil.isBlank(fileCode)) {
            fileCode = comFileRepoProc.getFileCode(id);
        }
        if (StrUtil.isBlank(fileCode)) {
            return ApiResult.fail("文件不存在");
        }

        try {
            rpcFileService.download(fileCode, outputStream);
            return null;
        } catch (Exception e) {
            logger.error("下载文件失败：", e);
            return ApiResult.fail("下载文件失败");
        }
    }

    @Override
    public ApiResult<ComFileDTO> query(Long id) {
        if (id == null) {
            return ApiResult.fail("文件ID为空");
        }

        Optional<ComFileDO> optionalFileDO = fileUploadInfoRepository.findById(id);
        if (optionalFileDO.isEmpty()) {
            return ApiResult.fail("文件不存在");
        }
        ComFileDO fileDO = optionalFileDO.get();

        // 组装后返回
        ComFileDTO fileDTO = new ComFileDTO();
        fileDTO.setId(fileDO.getId());
        fileDTO.setOriginalName(fileDO.getOriginalName());
        fileDTO.setUploadName(fileDO.getUploadName());
        fileDTO.setFileSize(fileDO.getFileSize());
        fileDTO.setSuffix(fileDO.getSuffix());

        return ApiResult.ok(fileDTO);
    }

    @Override
    public ApiResult<Map<Long, ComFileDTO>> query(List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return ApiResult.fail("文件ID为空");
        }

        Map<Long, ComFileDTO> fileDTOMap = fileUploadInfoRepository.findAllById(ids)
                .stream()
                .map(fileDO -> {
                    ComFileDTO fileDTO = new ComFileDTO();
                    fileDTO.setId(fileDO.getId());
                    fileDTO.setOriginalName(fileDO.getOriginalName());
                    fileDTO.setUploadName(fileDO.getUploadName());
                    fileDTO.setFileSize(fileDO.getFileSize());
                    fileDTO.setSuffix(fileDO.getSuffix());

                    return fileDTO;
                })
                .collect(Collectors.toMap(ComFileDTO::getId, t -> t, (t1, t2) -> t1));
        return ApiResult.ok(fileDTOMap);
    }

    /**
     * 上传原始文件
     *
     * @param multipartFile 原文件
     * @param fileDO        文件持久化信息
     */
    private void uploadOriginal(MultipartFile multipartFile, ComFileDO fileDO) {
        // 转换缩略图并上传
        ApiResult<StartFileUploadVO> rpcResult = null;
        try {
            rpcResult = rpcFileService.upload(multipartFile.getResource());
        } catch (Exception e) {
            logger.error("上传图片失败：", e);
            throw new BusinessException("上传图片失败：", e);
        }
        if (!rpcResult.isSuccess()) {
            throw new BusinessException("上传图片失败：" + rpcResult.getMsg());
        }

        // 将缩略图信息赋值到待持久化信息中
        StartFileUploadVO fileDTO = rpcResult.getData();
        fileDO.setFilePath(fileDTO.getFileUrl());
        fileDO.setFileCode(fileDTO.getFileCode());
        fileDO.setFileUrl(fileDTO.getDownloadPath());
    }

    /**
     * 上传缩略图
     *
     * @param multipartFile 原文件
     * @param fileDO        文件持久化信息
     */
    private void uploadThumb(MultipartFile multipartFile, ComFileDO fileDO) {
        // 判断是否支持生成缩略图
        if (!sysConfigProperties.getUpload().isThumb()) {
            logger.debug("未开启上传缩略图");
            return;
        }
        if (!supportThumb(multipartFile)) {
            logger.debug("不支持生成缩略图的文件");
            return;
        }

        // 转换缩略图并上传
        ApiResult<StartFileUploadVO> rpcResult = null;
        try {
            rpcResult = rpcFileService.upload(generateThumb(multipartFile));
        } catch (Exception e) {
            logger.error("上传图片的缩略图失败：", e);
            throw new BusinessException("上传图片的缩略图失败：" + e.getMessage());
        }
        if (!rpcResult.isSuccess()) {
            throw new BusinessException("上传图片的缩略图失败：" + rpcResult.getMsg());
        }

        // 将缩略图信息赋值到待持久化信息中
        StartFileUploadVO fileDTO = rpcResult.getData();
        fileDO.setThumbnailFileCode(fileDTO.getFileCode());
        fileDO.setThumbnailFileUrl(fileDTO.getDownloadPath());
    }

    /**
     * 判断是否支持生成缩略图
     *
     * @param multipartFile 上传文件
     * @return 支持与否
     */
    private boolean supportThumb(MultipartFile multipartFile) {
        return supportThumbSuffix.contains(getFileExt(multipartFile.getOriginalFilename()));
    }

    /**
     * 生成缩略图
     *
     * @param multipartFile 原文件
     * @return 缩略图
     */
    private Resource generateThumb(MultipartFile multipartFile) throws Exception {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        Thumbnails.of(multipartFile.getInputStream())
                .size(sysConfigProperties.getUpload().getThumbWidth(), sysConfigProperties.getUpload().getThumbHeight())
                .toOutputStream(outputStream);

        return new ByteArrayResource(outputStream.toByteArray()) {
            @Override
            public String getFilename() {
                return multipartFile.getOriginalFilename();
            }
        };
    }

    /**
     * 上传前的校验
     *
     * @param multipartFile 上传的文件
     */
    private ComFileDO obtainFileDo(MultipartFile multipartFile) {
        Assert.notNull(multipartFile, "上传文件为空");

        ComFileDO fileDO = new ComFileDO();
        SysConfigProperties.Upload upload = sysConfigProperties.getUpload();

        // 校验文件合法性
        String fileName = multipartFile.getOriginalFilename();
        String ext = getFileExt(fileName);
        if (CollectionUtils.isNotEmpty(upload.getBlockSuffixList())) {
            if (StrUtil.isNotBlank(ext) && upload.getBlockSuffixList().contains(ext)) {
                throw new IllegalArgumentException("该文件类型不允许上传");
            }
        }
        if (upload.getSizeLimit() != null) {
            if (upload.getSizeLimit() < multipartFile.getSize()) {
                throw new IllegalArgumentException("上传文件的大小超过限制");
            }
        }

        fileDO.setOriginalName(fileName);
        fileDO.setSuffix(ext);
        fileDO.setFileSize(multipartFile.getSize());

        String mimeType = getFileMimeType(fileName);
        fileDO.setMimeType(Assert.notBlank(mimeType, "未知文件类型"));

        return fileDO;
    }

    /**
     * 根据文件名获取后缀
     *
     * @param fileName 文件名
     * @return 扩展名
     */
    private String getFileExt(String fileName) {
        String ext = FilenameUtils.getExtension(fileName);
        return StrUtil.isBlank(ext) ? null : ext.toLowerCase();
    }

    /**
     * 获取文件类型
     *
     * @param fileName 文件名
     * @return
     */
    private String getFileMimeType(String fileName) {
        return FileUtil.getMimeType(fileName);
    }

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private SysConfigProperties sysConfigProperties;
    @Autowired
    private RpcFileService rpcFileService;
    @Autowired
    private ComFileRepo fileUploadInfoRepository;
    @Autowired
    private ComFileRepoProc comFileRepoProc;
}
